const { isMemberExpression } = require('@babel/types');

const fs = require('fs'),
    { parse } = require("@babel/parser"),
    { NodePath } = require("@babel/traverse"),
    traverse = require("@babel/traverse").default,
    types = require("@babel/types"),
    core = require("@babel/core"),
    generator = require("@babel/generator").default,
    cheerio = require('cheerio'),
    html_beautify = require('js-beautify').html,
    glob = require("glob"),
    args = process.argv.splice(2);

//判断是否常量
var isConstVar = function (v) {
    if (v instanceof NodePath) {
        const { confident, value } = v.evaluate();
        return confident;
    } else
        return types.isLiteral(v);
};
//判断函数调用的参数是否都是常量
var isConstArg = function (argArray) {
    for (let i = 0; i < argArray.length; i++)
        if (!isConstVar(argArray[i])) return false;
    return true;
};
//根据binding.constantViolations检查是否多次赋值，返回true表示未多次赋值
var assignTimes = function (constantViolations, varPath/*VariableDeclarator (v=...)*/) {
    if (!constantViolations) return 0;
    if (varPath) {
        let t = 0;
        for (let p of constantViolations) if (p != varPath) t++;
        return t;
    }
    return constantViolations.length;
}
//抛出异常
var astCode/*, skipStep = true*/;
var stopWithLog = function (stage, err, path) {
    console.log("error found at stage:" + stage);
    console.log(err);
    console.log(generator(path.node).code);
    console.log("line:" + path.node.loc.start.line + " column:" + path.node.loc.start.column);
    let lines = astCode.split("\n");
    let ln = path.node.loc.start.line - 1, lnEnd = path.node.loc.end.line - 1;
    for (let i = Math.max(0, ln - 3); i < ln; i++) console.log(lines[i]);
    console.log("----");
    for (let i = ln; i <= lnEnd; i++) console.log(lines[i]);
    let sp = ''; for (let i = 0; i < path.node.loc.start.column; i++) sp += ' '; console.log(sp + '^');
    console.log("----");
    for (let i = lnEnd + 1; i <= Math.min(lnEnd + 3, lines.length); i++) console.log(lines[i]);
    throw err;
};
var isPosition = function (path, line, column) {
    return path.node.loc.start.line === line && path.node.loc.start.column === column;
}
//生成常量节点
var makeConstNode = function (nodeValue) {
    let node = types.valueToNode(nodeValue);
    if (types.isStringLiteral(node)) {
        const dicControls = {
            "\0": "\\0", "\b": "\\b", "\t": "\\t", "\n": "\\n", "\v": "\\v", "\f": "\\f", "\r": "\\r",
            "\\": "\\\\", "\"": "\\\""
        };
        let value = "";
        for (let i = 0; i < nodeValue.length; i++) {
            let ch = nodeValue[i];
            if (dicControls[ch]) value += dicControls[ch];
            else if (/[\x20-\x7e\u4e00-\u9fa5]/.test(ch)) value += ch;
            else if (/[\x00-\x1f\x7f-\xff]/.test(ch))
                value += "\\x" + (ch.charCodeAt() + 0x100).toString(16).substr(1)
            else
                value += "\\u" + (ch.charCodeAt() + 0x10000).toString(16).substr(1);
        }
        node.extra = { rawValue: nodeValue, raw: "\"" + value + "\"" };
    }
    else if (types.isNumericLiteral(node))
        node.extra = { rawValue: node.value, raw: node.value.toString() };
    else if (types.isRegExpLiteral(node))
        node.extra = { rawValue: node.value, raw: nodeValue.toString() };
    return node;
};

//取反表达式
var notExpression = function (exp) {
    if (types.isUnaryExpression(exp, { operator: "!" })) return exp.argument;
    let logicalOper, binaryOper;
    if (types.isLogicalExpression(exp)) switch (exp.operator) {
        case "&&": logicalOper = "||"; break;
        case "||": logicalOper = "&&"; break;
    } else if (types.isBinaryExpression(exp)) switch (exp.operator) {
        case "==": binaryOper = "!="; break;
        case "===": binaryOper = "!=="; break;
        case "!=": binaryOper = "=="; break;
        case "!==": binaryOper = "==="; break;
        case ">": binaryOper = "<="; break;
        case "<": binaryOper = ">="; break;
        case ">=": binaryOper = "<"; break;
        case "<=": binaryOper = ">"; break;
    }
    if (logicalOper)
        return types.logicalExpression(logicalOper, notExpression(exp.left), notExpression(exp.right));
    if (binaryOper)
        return types.binaryExpression(binaryOper, exp.left, exp.right);
    return types.unaryExpression("!", exp);
}
//逻辑表达式转if
var isConvertableLogical = function (node) {
    return types.isLogicalExpression(node) && (node.operator === '&&' || node.operator === '||');
}
var LogicalToIf = function (node) {
    let test = node.left; if (node.operator === '||') test = notExpression(test);     //将条件取反
    return types.ifStatement(test, ExpressionToStatement(node.right));
}
var ExpressionToStatement = function (node) {
    if (isConvertableLogical(node))
        return LogicalToIf(node);
    else if (types.isExpressionStatement(node)) {
        if (isConvertableLogical(node.expression))
            return LogicalToIf(node.expression);
        else if (types.isSequenceExpression(node.expression))
            return SequenceToBlock(node.expression);
        return node;
    } else if (types.isSequenceExpression(node))
        return SequenceToBlock(node);
    return types.expressionStatement(node);
}

//逗号表达式转BlockStatement
var SequenceToBlock = function (node) {
    let tmp = [];
    for (let i = 0; i < node.expressions.length; i++)
        tmp.push(ExpressionToStatement(node.expressions[i]));
    return types.blockStatement(tmp);
}



let insSymbols = [], delSymbols = [];
var procDecode = function (data, outputName, outputExt, requireSteps) {
    var jsType;
    var ast = parse(data), func1, func2, funcinit
    /**
     * var _0x3034oM = function _0x3034oM() {...}();            func1
     * var _oM$ = function(__oM$) {...}(_0x3034oM);             func2
     * !function(o){...}();                                     ast
     */
    if (ast.program.body.length >= 3 &&
        types.isVariableDeclaration(ast.program.body[0]) && ast.program.body[0].declarations.length == 1 &&
        types.isVariableDeclaration(ast.program.body[1]) && ast.program.body[1].declarations.length == 1) {
        jsType = 0;
        func1 = ast.program.body[0];    //var _0x3034cB = function _0x3034cB(){...}();
        func2 = ast.program.body[1];    //var _cB$ = function(__cB$){...}(_0x3034cB);
    }
    /**
     * var _0jG$ = [...], _jG$ = function() {...}();            func1
     * if (...) {...} else {...}                                func2
     * (function(undefined) {...})();                           ast
     */
    if (jsType === undefined && ast.program.body.length >= 2 &&
        types.isVariableDeclaration(ast.program.body[0]) && ast.program.body[0].declarations.length == 2) {
        for (let i = 1; i < ast.program.body.length; i++) if (types.isIfStatement(ast.program.body[i])) {
            func1 = ast.program.body[0];    //var _0jG$ = [...], _jG$ = function() {...}();
            func2 = ast.program.body[i];    //if (...) {...} else {...}
            jsType = 1;
            break;
        }
    }
    /**
     * function a04c7a9b(_$m, _$s) {...}                        func2
     * (function(_$m, _$s){...}(...),                           funcinit
     *  function(undefined){...}()                              ast
     * )
     * function a04c7a9u(){...}                                 func1
     */
    if (jsType === undefined) {
        /**
         * a02cbb1c = function() {...}
         * return a02cbb1c();
         * 或者
         * return a02cbb1c = function() {...}, a02cbb1c();
         */
        let isRetFunc = function (body, index, checkFunc) {
            let exp, cal;
            if (types.isExpressionStatement(body[index]) && index + 1 < body.length && types.isReturnStatement(body[index + 1])) {
                exp = body[index].expression; cal = body[index + 1].argument;
            } else if (types.isReturnStatement(body[index])) {
                if (!types.isSequenceExpression(body[index].argument) || body[index].argument.expressions.length != 2) return false;
                exp = body[index].argument.expressions[0]; cal = body[index].argument.expressions[1];
            } else
                return false;
            //a02cbb1c = function() {...}
            if (!types.isAssignmentExpression(exp)) return false;
            if (exp.operator != "=" || !types.isIdentifier(exp.left) || !types.isFunctionExpression(exp.right)) return false;
            if (!checkFunc(exp.right)) return false;
            //a02cbb1c();
            if (!types.isCallExpression(cal)) return false;
            if (cal.arguments.length != exp.right.params.length || !types.isIdentifier(cal.callee, { name: exp.left.name })) return false;
            return true;
        }
        /**
         * function a02cbb1c() {
         *    var t = ['CMvHzhLtDgf0zq', 'DxnLCKfNzw50', ...];
         *    a02cbb1c = function() {
         *        return t;
         *    };
         *    return a02cbb1c();
         * }
         */
        let isFunc1 = function (node) {
            if (!types.isFunctionDeclaration(node)) return false;
            if (!types.isIdentifier(node.id) || !types.isBlockStatement(node.body)) return false;
            let { body } = node.body;
            if (body.length < 2) return false;
            //var t = ['CMvHzhLtDgf0zq', 'DxnLCKfNzw50', ...];
            if (!types.isVariableDeclaration(body[0]) || body[0].declarations.length != 1) return false;
            let declarator = body[0].declarations[0];
            if (!types.isVariableDeclarator(declarator) || !types.isIdentifier(declarator.id) || !types.isArrayExpression(declarator.init)) return false;
            if (!isConstArg(declarator.init.elements)) return false;
            return isRetFunc(body, 1, function (func) {
                // a02cbb1c = function() {
                //     return t;
                // };
                if (func.params.length != 0 || !types.isBlockStatement(func.body) || func.body.body.length != 1) return false;
                let ret = func.body.body[0];
                if (!types.isReturnStatement(ret) || !types.isIdentifier(ret.argument, { name: declarator.id.name })) return false;
                return true;
            });
        }
        /**
         * function a02cbb1F(_$p, _$O) {
         *     var _$c = a02cbb1c();
         *     return a02cbb1F = function(_$F, _$i) {...}, a02cbb1F(_$p, _$O);
         * }
         */
        let isFunc2 = function (node, func1Name) {
            if (!types.isFunctionDeclaration(node)) return false;
            if (!types.isIdentifier(node.id) || !types.isBlockStatement(node.body)) return false;
            let { body } = node.body;
            if (body.length < 2) return false;
            //var _$c = a02cbb1c();
            if (!types.isVariableDeclaration(body[0]) || body[0].declarations.length != 1) return false;
            let declarator = body[0].declarations[0];
            if (!types.isVariableDeclarator(declarator) || !types.isIdentifier(declarator.id) || !types.isCallExpression(declarator.init)) return false;
            let calExp = declarator.init;
            if (!types.isIdentifier(calExp.callee, { name: func1Name }) || calExp.arguments.length != 0) return false;
            return isRetFunc(body, 1, function (func) {
                // aa02cbb1F = function(_$F, _$i) {...}
                return func.params.length == 2 && types.isBlockStatement(func.body);
            });
        }
        /**
         * function(_$p, _$O) {
         *   var f = a02cbb1F,
         *   _$c = _$p();
         *   while() { ... }
         * } (a02cbb1c, 0xcf396 + -0x5c0d6 + -0xe8f * 0x1);
         */
        let isFuncInit = function (node, func1Name, func2Name) {
            if (!types.isCallExpression(node)) return false;
            //function(_$p, _$O){...}
            let { callee, arguments } = node;
            if (!types.isFunctionExpression(callee) || callee.params.length != 2 || !types.isIdentifier(callee.params[0])) return false;
            if (!types.isBlockStatement(callee.body)) return false;
            let { body } = callee.body;
            if (body.length != 2) return false;
            //var f = a02cbb1F, _$c = _$p();
            if (!types.isVariableDeclaration(body[0]) || body[0].declarations.length != 2) return false;
            if (!types.isVariableDeclarator(body[0].declarations[0]) ||
                !types.isIdentifier(body[0].declarations[0].init, { name: func2Name })) return false;
            if (!types.isVariableDeclarator(body[0].declarations[1]) ||
                !types.isCallExpression(body[0].declarations[1].init) ||
                !types.isIdentifier(body[0].declarations[1].init.callee, { name: callee.params[0].name })) return false;
            //while() { ... }
            if (!types.isWhileStatement(body[1])) return false;
            //(a02cbb1c, 0xcf396 + -0x5c0d6 + -0xe8f * 0x1)
            if (arguments.length != 2 || !types.isIdentifier(arguments[0], { name: func1Name })) return false;
            return true;
        }

        func1 = func2 = funcinit = undefined;
        for (node of ast.program.body) {
            if (isFunc1(node)) {
                if (func1 === undefined) func1 = node; else { func1 = undefined; break; }
            }
        }
        if (func1) for (node of ast.program.body) {
            if (isFunc2(node, func1.id.name)) {
                if (func2 === undefined) func2 = node; else { func2 = undefined; break; }
            }
        }
        if (func1 && func2) for (node of ast.program.body) {
            if (!types.isExpressionStatement(node)) continue;
            if (types.isSequenceExpression(node.expression)) {
                for (exp of node.expression.expressions) if (isFuncInit(exp, func1.id.name, func2.id.name)) {
                    if (funcinit === undefined) funcinit = exp; else { funcinit = null; break; }
                }
                if (funcinit === null) { funcinit = undefined; break; }
            } else if (isFuncInit(node.expression, func1.id.name, func2.id.name)) {
                if (funcinit === undefined) funcinit = node.expression; else { funcinit = undefined; break; }
            }
        }
        if (func1 && func2 && funcinit) jsType = 2;
    }
    if (jsType === undefined) {
        if (copyAnyway) return;
        console.log("WARNING: no init part found, try to decode...");
        jsType = 2;
    }

    //删除前2个定义
    traverse(ast, {
        VariableDeclaration(path) {
            const node = path.node;
            if ((jsType === 0 && (node === func1 || node === func2)) || (jsType === 1 && node === func1))
                path.remove();
        },
        IfStatement(path) {
            const node = path.node;
            if (jsType === 1 && node === func2)
                path.remove();
        },
        FunctionDeclaration(path) {
            const node = path.node;
            if (jsType === 2 && (node === func1 || node === func2))
                path.remove();
        },
        CallExpression(path) {
            const node = path.node;
            if (jsType === 2 && node === funcinit)
                path.remove();
        },
    });

    let data1, func2Code, varName;
    if (jsType === 0) {
        /**
        * var _oM$ = function(__oM$) {...}(_0x3034oM);
        */
        func2Code = generator(func2, { retainLines: true }).code;
        varName = func2.declarations[0].id.name;     //_cA$
        /**
         * var _0x3034oM = function _0x3034oM() {...}();
         * var _oM$ = function(__oM$) {...}(_0x3034oM);
        */
        data1 = generator(func1, { retainLines: true }).code + func2Code;
    } else if (jsType === 1) {
        /**
         * var _0jG$ = [...], _jG$ = function() {...}();
         */
        data1 = generator(func1, { retainLines: true }).code;
        /**
         * var _jG$ = function() {...}();
         */
        func2 = func1.declarations[1];
        varName = func2.id.name;     //_jG$
        func2Code = "var " + generator(func2, { retainLines: true }).code;
    } else if (jsType === 2 && func2) {
        varName = func2.id.name;    //a04c7a9b
        /**
         * function a04c7a9u(){...}
         * function a04c7a9b(_$m, _$s) {...}
         * (function(_$m, _$s){...})(...)
         */
        data1 = generator(func1, { retainLines: true }).code + generator(func2, { retainLines: true }).code +
            "(" + generator(funcinit, { retainLines: true }).code + ")";
        // if (types.isExpressionStatement(ast.program.body[0]) && types.isSequenceExpression(ast.program.body[0].expression)) {
        //     let exps = ast.program.body[0].expression.expressions;
        //     if (exps.length >= 2) {
        //         let nd = exps[0];   //function(_$m, _$s){...}
        //         data1 += "(" + generator(nd, { retainLines: true }).code + ")";
        //         traverse(ast, {
        //             CallExpression(path) {
        //                 const node = path.node;
        //                 if (node === nd)
        //                     path.remove();
        //             }
        //         });
        //     }
        // }
    }
    if (requireSteps) {
        if (data1)
            fs.writeFileSync(outputName + '_part1' + outputExt, generator(parse(data1)).code, 'utf8');
        fs.writeFileSync(outputName + '_part2' + outputExt, generator(ast).code, 'utf8');
    }
    if (data1) {
        var fn = console.log;
        eval(data1);
        console.log = fn;
    }

    //输出文件
    var outputAST = function (fname) {
        astCode = generator(ast).code;
        if (fname) fs.writeFileSync(fname, astCode, 'utf8');
        ast = parse(astCode);
    };
    var readAST = function (fname) {
        astCode = fs.readFileSync(fname, 'utf8');
        ast = parse(astCode);
    }

    var constVar = {}, sipFuncs = {}, arrayVar = {};
    if (jsType === 0 || jsType === 1) {
        let decodeFunc, func2Path = parse(func2Code);   //重新生成语法树
        func2 = func2Path.program.body[0];
        if (jsType === 0) {
            let func2Body = func2.declarations[0].init.callee.body.body;
            //解码函数是function体内唯一一个VariableDeclaration 
            if (func2Body != null) for (let i = 0; i < func2Body.length; i++) if (types.isVariableDeclaration(func2Body[i])) {
                //解码函数定义为 var c = function c(_$cA){...} or var c = function c(__$cA){...}
                if (func2Body[i].declarations[0].init.arguments.length != 1 ||
                    !types.isIdentifier(func2Body[i].declarations[0].init.arguments[0]) ||
                    (func2Body[i].declarations[0].init.arguments[0].name !== varName &&
                        func2Body[i].declarations[0].init.arguments[0].name !== "_" + varName)) continue;
                if (decodeFunc === undefined)
                    decodeFunc = func2Body[i].declarations[0].init.callee;
                else
                    decodeFunc = null;
            }
        } else if (jsType === 1) {
            decodeFunc = func2.declarations[0].init.callee;
        }
        if (decodeFunc === undefined || decodeFunc === null) {
            console.log("Decode function not found"); return;
        }
        //遍历decodeFunc查找常量定义及函数定义
        traverse(func2Path, {
            Property(path) {
                if (path.getFunctionParent().node !== decodeFunc)   //不是decodeFunc定义的属性不处理
                    return;
                const node = path.node;
                const { confident, value } = path.get("value").evaluate();
                if (confident) {    //常量 ba: 33
                    if (value.constructor === Array)
                        arrayVar[node.key.name] = value;
                    else
                        constVar[node.key.name] = value;
                } else if (types.isFunctionExpression(node.value)) {     //ah: function(x, y){ return x === y; }
                    let func = node.value.body;
                    if (func.body.length === 1 && types.isReturnStatement(func.body[0])) {
                        sipFuncs[node.key.name] = func.body[0].argument;
                    }
                } else {    //某个函数执行后返回常量字符串数组
                    let obj = eval(varName + "." + node.key.name);
                    if (obj && obj.constructor === Array)
                        arrayVar[node.key.name] = obj;
                }
            }
        });
        //替换常量 _cB$.ba => 33
        let part3Array = [];
        traverse(ast, {
            MemberExpression(path) {
                try {
                    let node = path.node, constName;
                    if (!types.isIdentifier(node.object, { name: varName })) return;
                    if (types.isIdentifier(node.property)) {
                        constName = node.property.name;
                    } else if (types.isLiteral(node.property)) {
                        constName = node.property.value;
                    }
                    if (!constName || !constVar.hasOwnProperty(constName)) return;
                    let rep = makeConstNode(constVar[constName]);
                    part3Array.push({ path, rep });
                } catch (err) {
                    stopWithLog("part3", err, path);
                }
            }
        });
        for (let v of part3Array) v.path.replaceWith(v.rep);
        outputAST(requireSteps ? (outputName + "_part3" + outputExt) : undefined);

        //判断是否为_cB$.xx属性
        var isVarNameMember = function (node) {
            if (!types.isMemberExpression(node)) return false;
            return types.isIdentifier(node.object, { name: varName });
        }
        //判断是否为_cB$.xx调用
        let isVarNameCall = function (node) {
            if (!types.isCallExpression(node)) return false;
            return isVarNameMember(node.callee);
        };
        //替换函数
        traverse(ast, {
            CallExpression(path) {
                try {
                    if (!isVarNameCall(path.node)) return;
                    let { callee, arguments } = path.node;
                    var funcName;
                    if (types.isIdentifier(callee.property)) {
                        if (sipFuncs.hasOwnProperty(callee.property.name)) funcName = callee.property.name; else return;
                    } else if (types.isLiteral(callee.property)) {
                        if (sipFuncs.hasOwnProperty(callee.property.value)) funcName = callee.property.value; else return;
                    } else
                        return;

                    let retValue = sipFuncs[funcName];
                    if (types.isCallExpression(retValue) && arguments.length > 0) {
                        //构造节点
                        path.replaceWith(types.CallExpression(arguments[0], arguments.slice(1)));
                    } else if (types.isBinaryExpression(retValue) && arguments.length === 2) {
                        //_cB$.ah(x,y) => x===y
                        let rep = types.BinaryExpression(retValue.operator, arguments[0], arguments[1]);
                        path.replaceWith(rep);
                    } else if (types.isLogicalExpression(retValue) && arguments.length === 2) {
                        //构造节点
                        let rep = types.LogicalExpression(retValue.operator, arguments[0], arguments[1]);
                        path.replaceWith(rep);
                    } else {    //_cB$.s(_cB$.at) => "q"
                        let args = path.get("arguments");
                        if (isConstArg(args) && args.length > 0) {    //无参数的函数调用不做替换
                            let value = eval(generator(path.node).code);
                            let rep = makeConstNode(value);
                            path.replaceWith(rep);
                        }
                    }
                } catch (err) {
                    stopWithLog("part4", err, path);
                }
            }
        });
        outputAST(requireSteps ? (outputName + "_part4" + outputExt) : undefined);
    }
    else if (jsType === 2) {
        // if (!skipStep) {
        //将常量表达式替换成计算结果值
        let rary = [];
        traverse(ast, {
            BinaryExpression(path) {
                if (!isConstVar(path.get("left")) || !isConstVar(path.get("right"))) return;
                for (let p = path; p != null; p = p.parentPath) if (rary.includes(p)) return;
                rary.push(path);
            }
        });
        for (let p of rary) p.replaceWith(makeConstNode(p.evaluate().value));
        outputAST(requireSteps ? (outputName + "_part2_1" + outputExt) : undefined);

        //将调用外部函数替换为调用后的结果值
        let replaceData = function (path/*Identifier*/) {
            let node = path.parentPath.node;
            if (types.isVariableDeclarator(node) && path.key === "init") { //var Q=a04c7a9b
                path = path.parentPath;
                const binding = path.scope.getBinding(path.node.id.name);
                if (assignTimes(binding.constantViolations, path) > 0) return;  //已赋初值的变量的值被修改则不处理
                for (const refer_path of binding.referencePaths) replaceData(refer_path); //替换引用
            } else if (types.isCallExpression(node) && path.key === "callee") {  //a04c7a9b(0x7b)
                path = path.parentPath;
                let args = path.get("arguments");
                if (!isConstArg(args)) return;
                let ev = varName + "(";
                for (let i = 0; i < args.length; i++) {
                    if (i > 0) ev += ",";
                    ev += args[i].evaluate().value;
                }
                ev += ");"
                let rep = makeConstNode(eval(ev));
                rary.push({ path, rep });
            } else
                console.log("unknown refrence of " + varName + "\n" + generator(node).code);
        };
        rary = [];
        traverse(ast, {
            Identifier(path) {
                if (types.isIdentifier(path.node, { name: varName })) replaceData(path);
            }
        });
        for (let p of rary) p.path.replaceWith(p.rep);
        outputAST(requireSteps ? (outputName + "_part2_2" + outputExt) : undefined);

        //删除对外部函数调用定义的变量
        let rmvData = function (path/*Identifier*/) {
            let node = path.parentPath.node;
            if (types.isVariableDeclarator(node) && path.key === "init") { //var Q=a04c7a9b
                path = path.parentPath;
                const binding = path.scope.getBinding(path.node.id.name);
                if (assignTimes(binding.constantViolations, path) > 0) return;  //已赋初值的变量的值被修改则不处理
                if (binding.referencePaths.length <= 0)
                    rary.push(path);
                else
                    for (const refer_path of binding.referencePaths) rmvData(refer_path);
            }
        }
        for (; ;) {
            rary = [];
            traverse(ast, {
                Identifier(path) {
                    if (types.isIdentifier(path.node, { name: varName })) rmvData(path);
                }
            });
            if (rary.length <= 0) break;
            for (let p of rary) p.remove();
            outputAST(undefined);
        }
        outputAST(requireSteps ? (outputName + "_part2_3" + outputExt) : undefined);

        //遍历查找常量定义及函数定义并替换
        let checkedp = [];
        for (let id = 1; ;) {
            let pfound;
            traverse(ast, {
                VariableDeclarator(path) {
                    if (pfound) { path.stop(); return; }
                    let node = path.node;
                    if (!types.isIdentifier(node.id) || !types.isObjectExpression(node.init)) return;
                    let props = path.get("init").get("properties"), code = generator(node).code;
                    if (checkedp.includes(code)) return;    //已经检查过
                    let cVar = {}, sFunc = {};
                    for (let i = 0; i < props.length; i++) {
                        let pnode = props[i].node;
                        if (!types.isLiteral(pnode.key)) {
                            checkedp.push(code);
                            return;
                        }
                        const { confident, value } = props[i].get("value").evaluate();
                        if (confident) {    //常量 'RMNcw': '100'
                            if (pnode.key.value === "toString") {
                                value = value;
                            }
                            cVar[pnode.key.value] = value;
                            continue;
                        } else if (types.isFunctionExpression(pnode.value)) {     //函数 'TnyIo': function (_$q, _$t){ return _$q === _$t; }
                            let func = pnode.value.body;
                            if (func.body.length === 1 && types.isReturnStatement(func.body[0])) {
                                sFunc[pnode.key.value] = func.body[0].argument;
                                continue;
                            }
                        }
                        return; //存在不符合要求的属性，该定义忽略
                    }
                    const binding = path.scope.getBinding(node.id.name);
                    if (assignTimes(binding.constantViolations, path) > 0) return;  //已赋初值的变量的值被修改则不处理
                    pfound = { path, binding, cVar, sFunc };
                    checkedp.push(code);
                    path.stop();
                }
            });
            if (!pfound) break;
            rary = [];
            pfound.haveErr = false;
            for (const refer_path of pfound.binding.referencePaths) {
                let path = refer_path.parentPath, node = path.node, rep;
                if (refer_path.key === "object" && types.isMemberExpression(node) && types.isLiteral(node.property)) {
                    if (pfound.cVar.hasOwnProperty(node.property.value)) { //替换常量
                        rep = makeConstNode(pfound.cVar[node.property.value]);
                    } else if (pfound.sFunc.hasOwnProperty(node.property.value)) { //替换函数
                        let fpath = path.parentPath, fnode = fpath.node;
                        if (path.key === "callee" && types.isCallExpression(fnode)) {
                            path = fpath;
                            let arguments = fnode.arguments;
                            let retValue = pfound.sFunc[node.property.value];
                            if (types.isCallExpression(retValue) && arguments.length > 0) {
                                //构造节点
                                rep = types.CallExpression(arguments[0], arguments.slice(1));
                            } else if (types.isBinaryExpression(retValue) && arguments.length === 2) {
                                //_cB$.ah(x,y) => x===y
                                rep = types.BinaryExpression(retValue.operator, arguments[0], arguments[1]);
                            } else if (types.isLogicalExpression(retValue) && arguments.length === 2) {
                                //构造节点
                                rep = types.LogicalExpression(retValue.operator, arguments[0], arguments[1]);
                            } else {    //_cB$.s(_cB$.at) => "q"
                                let args = path.get("arguments");
                                if (isConstArg(args) && args.length > 0) {    //无参数的函数调用不做替换
                                    let value = eval(generator(path.node).code);
                                    rep = makeConstNode(value);
                                }
                            }
                        }
                    }
                }
                if (!rep) { pfound.haveErr = true; break; }
                rary.push({ path, rep });
            }

            var replacePath = function (ary/*[{path,rep},...]*/) {
                for (let p of ary)
                    p.path.findParent((path) => {
                        for (let q of ary) if (p != q && path === q.path) {
                            if (q.son === undefined) q.son = [];
                            q.son.push(p);
                            p.father = q;
                            return true;
                        }
                        return false;
                    });
                for (let p of ary) if (!p.father) replaceNode(p);
            }
            var replaceNode = function (p) {
                p.path.replaceWith(p.rep);
                if (p.son) for (let s of p.son) {
                    let newPath;
                    p.path.traverse({
                        MemberExpression(path) {    //常量替换
                            if (newPath) { path.stop(); return; }
                            if (path.node === s.path.node) { newPath = path; path.stop(); }
                        },
                        CallExpression(path) {  //函数替换
                            if (newPath) { path.stop(); return; }
                            if (path.node === s.path.node) { newPath = path; path.stop(); }
                        }
                    });
                    if (newPath) {
                        s.path = newPath;
                        replaceNode(s);
                    } else {
                        console.log("not replaced:" + generator(s.path.node).code);
                        console.log("should replaced while replace " + generator(p.path.node).code);
                    }
                }
            }
            if (pfound.haveErr) continue;
            replacePath(rary);
            pfound.path.remove();
            outputAST(requireSteps ? (outputName + "_part3_" + id + outputExt) : undefined);
            id++;
        }

        traverse(ast, {
            UnaryExpression(path) {     //![]=>false
                try {
                    let { node } = path;
                    if (node.operator !== '!' || !types.isArrayExpression(node.argument) || node.argument.elements.length > 0) return;
                    path.replaceInline(types.booleanLiteral(false));
                } catch (err) {
                    stopWithLog("true/false", err, path);
                }
            }
        });
        traverse(ast, {
            UnaryExpression(path) {     //!false=>true
                try {
                    let { node } = path;
                    if (node.operator !== '!' || !types.isLiteral(node.argument, { value: false })) return;
                    path.replaceInline(types.booleanLiteral(true));
                } catch (err) {
                    stopWithLog("true/false", err, path);
                }
            }
        });
        outputAST(requireSteps ? (outputName + "_part4" + outputExt) : undefined);
        // } else {
        //     readAST(outputName + "_part4" + outputExt);
        // }
    }

    traverse(ast, {
        //逗号表达式拆分成语句
        ExpressionStatement(path) {
            try {
                let { expression } = path.node;
                if (!types.isSequenceExpression(expression)) return;
                let tmp = [];
                expression.expressions.forEach(express => {
                    tmp.push(types.ExpressionStatement(express))
                })
                path.replaceInline(tmp)
            } catch (err) {
                stopWithLog("part5", err, path);
            }
        }
    });
    if (jsType === 1) traverse(ast, {
        //替换数组常量
        MemberExpression(path) {
            try {
                let node = path.node;
                if (!isVarNameMember(node.object)) return;
                const { confident, value } = path.get("property").evaluate();
                if (!confident) return;
                let array;
                if (types.isIdentifier(node.object.property)) {
                    if (arrayVar.hasOwnProperty(node.object.property.name)) array = arrayVar[node.object.property.name]; else return;
                } else if (types.isLiteral(node.object.property)) {
                    if (arrayVar.hasOwnProperty(node.object.property.value)) array = arrayVar[node.object.property.value]; else return;
                } else
                    return;
                if (value >= 0 && value < array.length)
                    path.replaceWith(makeConstNode(array[value]));
            } catch (err) {
                stopWithLog("part5", err, path);
            }
        },
        //替换if (常量) 语句
        IfStatement(path) {
            const { confident, value } = path.get("test").evaluate();
            if (!confident) return;
            let { consequent, alternate } = path.node;
            if (consequent !== null && types.isBlockStatement(consequent) && consequent.body.length === 1)
                consequent = consequent.body[0];
            if (alternate !== null && types.isBlockStatement(alternate) && alternate.body.length === 1)
                alternate = alternate.body[0];
            if (value) {
                if (consequent !== null) path.replaceWith(consequent); else path.remove();
            } else {
                if (alternate !== null) path.replaceWith(alternate); else path.remove();
            }
        }
    });
    outputAST(requireSteps ? (outputName + "_part5" + outputExt) : undefined);

    //替换常数变量
    var part6Array = [], part6Del = [];
    traverse(ast, {
        VariableDeclarator(path) {//还原var、let、const 定义的变量
            try {
                let { id, init } = path.node;
                const binding = path.scope.getBinding(id.name);
                if (init === null) {
                    let parent = path.getStatementParent();     //判断是否是for (var a in ...;;), 此时init=null但不是常数变量
                    if (parent !== null && (types.isForInStatement(parent) || types.isForOfStatement(parent))) return;
                    if (assignTimes(binding.constantViolations) != 1) return; //未赋初值的变量找赋值语句，如赋值超过1次则不处理
                    let assign = binding.constantViolations[0];
                    if (!types.isAssignmentExpression(assign)) return;
                    init = assign.get("right").node;
                } else if (assignTimes(binding.constantViolations, path) > 0)   //已赋初值的变量的值被修改则不处理
                    return;
                if (init === null || !types.isLiteral(init)) return; //只处理字面量
                for (const refer_path of binding.referencePaths)  //替换引用
                    part6Array.push({ refer_path, init });
                if (binding.constantViolations.length == 1 && binding.constantViolations[0] !== path)
                    part6Del.push(binding.constantViolations[0]);     //删除首次赋值
                part6Del.push(path);  //删除var定义
            } catch (err) {
                stopWithLog("part6", err, path);
            }
        }
    });
    for (let v of part6Array) v.refer_path.replaceWith(v.init);
    for (let v of part6Del) v.remove();
    outputAST(requireSteps ? (outputName + "_part6" + outputExt) : undefined);

    var part7Array = [];
    if (jsType === 0) traverse(ast, {
        //替换for ... switch
        ForStatement(path) {
            try {
                let { init, test, update, body } = path.node;
                //判断for条件, 必须是 variable !== _cB$xx(pp)形式
                if (!types.isBinaryExpression(test) || test.operator !== '!==') return;
                let variable = test.left, func = test.right;
                if (!types.isIdentifier(variable) || !types.isCallExpression(func)) {
                    variable = test.right, func = test.left;
                    if (!types.isIdentifier(variable) || !types.isCallExpression(func)) return;
                }
                //调用_cB$xx(pp) or _cB$.xx() 且调用参数均为常量
                if (!isVarNameCall(func) || !isConstArg(func.arguments)) return;
                //判断body，必须是单个switch 且 switch的值为条件中的 variable
                if (!types.isSwitchStatement(body) || !types.isIdentifier(body.discriminant) ||
                    body.discriminant.name !== variable.name) return;
                //判断update，必须是null
                if (update !== null) return;
                let { cases } = body, caseValue = [], value, valueString,
                    binding = path.scope.getBinding(variable.name);
                let all_pre_siblings = path.getAllPrevSiblings();
                if (all_pre_siblings !== null) for (let sibling of all_pre_siblings) sibling.traverse({
                    VariableDeclarator(_path) {
                        let { id, init } = _path.node;
                        if (init === null || id.name !== variable.name ||
                            _path.scope.getBinding(variable.name) != binding) return;
                        if (value === undefined) {
                            valueString = generator(init).code;
                            value = eval(valueString);
                        } else    //多次赋值
                            value = null;
                    }
                });
                if (value === undefined && init !== null && types.isVariableDeclaration(init)) {  //之前没有定义，在for的init里查找
                    for (let declare of init.declarations) {
                        let { id, init } = declare;
                        if (init === null || id.name !== variable.name) continue;
                        if (value === undefined) {
                            valueString = generator(init).code;
                            value = eval(valueString);
                        } else    //多次赋值
                            value = null;
                    }
                }
                if (value === undefined || value === null) {      //没有找到初值定义
                    console.log("initvalue not found\n" + generator(path.node).code);
                    return;
                }
                let ret_body = [];
                if (init != null) {
                    //检查for(init;;)部分，如果有定义变量则执行，以防止在case中使用该变量的值
                    if (types.isVariableDeclaration(init))
                        for (let i = 0; i < init.declarations.length; i++) {
                            if (types.isVariableDeclarator(init.declarations[i]) && init.declarations[i].init !== null &&
                                types.isIdentifier(init.declarations[i].id) && init.declarations[i].id.name !== variable.name)
                                eval(generator(init.declarations[i]).code);
                        }
                    ret_body = ret_body.concat(init)
                        .concat(types.emptyStatement());    //加一个空语句以防止init部分与后续命令被错误连接
                }
                if (cases.length > 0) {
                    let defaultIdx = -1;
                    for (let i = 0; i < cases.length; i++) {
                        if (cases[i].test === null)
                            defaultIdx = i;
                        else {
                            caseValue[i] = eval(generator(cases[i].test).code); //检查case值是否重复
                            for (let j = 0; j < i; j++) if (j != defaultIdx && caseValue[i] === caseValue[j]) {
                                console.log("duplicate case value for " + generator(cases[j].test).code + " and " + generator(cases[i].test).code);
                                return;
                            }
                        }
                    }
                    let endValue = eval(generator(func).code), valueChanged = true;
                    while (value !== endValue) {
                        if (!valueChanged) {    //没有改变variable的值，无限循环
                            console.log("switch value unchanged " + valueString + "\n" + generator(path.node).code);
                            return;
                        }
                        valueChanged = false; let case_body;
                        for (let i = 0; i < cases.length; i++)
                            if (i != defaultIdx && caseValue[i] === value) {
                                case_body = cases[i].consequent; break;
                            }
                        if (case_body === undefined && defaultIdx >= 0)
                            case_body = cases[defaultIdx].consequent;
                        if (case_body === undefined || case_body === null || case_body.length <= 0) {
                            console.log("no case for " + valueString + "\n" + generator(path.node).code);
                            return;
                        }
                        for (let i = 0; i < case_body.length; i++) {
                            let stmt = case_body[i];
                            if (types.isContinueStatement(stmt) || types.isBreakStatement(stmt)) break;
                            if (types.isExpressionStatement(stmt)) {
                                let exp = stmt.expression;
                                if (isVarNameCall(exp)) {   //_cB$["r"]();
                                    eval(generator(stmt).code);
                                    continue;
                                }
                                if (types.isAssignmentExpression(exp) &&
                                    types.isIdentifier(exp.left) && exp.left.name === variable.name &&
                                    exp.operator === '=') {
                                    if (isVarNameCall(exp.right)) { //a = _cB$.q(85);
                                        valueString = generator(exp.right).code;
                                        value = eval(valueString); valueChanged = true;
                                        continue;
                                    }
                                    if (types.isConditionalExpression(exp.right) &&  //d = _cB$["r"]() ? _cB$.q("9") : _cB$["q"]("9");
                                        isVarNameCall(exp.right.test) &&
                                        isVarNameCall(exp.right.consequent) && isVarNameCall(exp.right.alternate)) {
                                        valueString = generator(
                                            eval(generator(exp.right.test).code) ? exp.right.consequent : exp.right.alternate).code;
                                        value = eval(valueString); valueChanged = true;
                                        continue;
                                    }
                                }
                            }
                            ret_body.push(stmt);
                            if (types.isReturnStatement(stmt)) {
                                value = endValue; valueChanged = true; break;
                            }
                        }
                    }
                }
                part7Array.push({ path, ret_body });
            } catch (err) {
                stopWithLog("part7", err, path);
            }
        }
    });
    else if (jsType === 1 || jsType === 2) traverse(ast, {
        //替换while ... switch
        WhileStatement(path) {
            try {
                let { test, body } = path.node;
                //判断while条件, 必须是while(true)形式
                if (!types.isLiteral(test, { value: true })) return;
                //判断body，必须是单个switch+单个break 且 switch的值为条件中的 variable
                if (!types.isBlockStatement(body) || body.body.length != 2 ||
                    !types.isSwitchStatement(body.body[0]) || !types.isBreakStatement(body.body[1])) return;
                let { discriminant, cases } = body.body[0], array, left, right;
                if (jsType === 1) {
                    //switch条件必须是 _jG$.c[_0xf++] ^ _n$jL; 其中_jG$.c为常量数组,_0xf和_n$jL为局部变量
                    if (!types.isBinaryExpression(discriminant) || discriminant.operator !== "^") return;
                    left = discriminant.left; right = discriminant.right;
                    if (!isMemberExpression(left) || !isVarNameMember(left.object) || !arrayVar.hasOwnProperty(left.object.property.name)) return;
                    array = arrayVar[left.object.property.name];
                } else if (jsType === 2) {
                    //switch条件必须是 (_$u[_$b++]); 其中_$u为局部数组变量(_$u = '1|6|2|7|5|3|4|0|8'.split('|')),_$b为局部变量
                    if (!types.isMemberExpression(discriminant)) return;
                    left = discriminant;
                }
                if (!types.isUpdateExpression(left.property) || left.property.operator !== "++") return;
                if (!types.isIdentifier(left.property.argument)) return;
                let valueString;
                let leftVar = left.property.argument.name, leftValue, leftBinding = path.scope.getBinding(leftVar);
                let rightVar, rightValue, rightBinding;
                let aryVar, aryBinding;
                if (jsType === 1) {
                    if (!types.isIdentifier(right)) return;
                    rightVar = right.name; rightBinding = path.scope.getBinding(rightVar);
                } else if (jsType === 2) {
                    if (!types.isIdentifier(left.object)) return;
                    aryVar = left.object.name; aryBinding = path.scope.getBinding(aryVar);
                }
                let all_pre_siblings = path.getAllPrevSiblings();
                if (all_pre_siblings !== null) for (let sibling of all_pre_siblings) sibling.traverse({
                    VariableDeclarator(_path) {
                        let { id, init } = _path.node;
                        if (init !== null) {
                            if (id.name === leftVar && _path.scope.getBinding(leftVar) === leftBinding) {
                                if (leftValue === undefined) {
                                    valueString = generator(init).code;
                                    leftValue = eval(valueString);
                                } else    //多次赋值
                                    leftValue = null;
                            } else if (jsType === 1 && id.name === rightVar && _path.scope.getBinding(rightVar) === rightBinding) {
                                if (rightValue === undefined) {
                                    valueString = generator(init).code;
                                    rightValue = eval(valueString);
                                } else    //多次赋值
                                    rightValue = null;
                            } else if (jsType === 2 && id.name === aryVar && _path.scope.getBinding(aryVar) === aryBinding) {
                                if (array === undefined) {
                                    valueString = generator(init).code;
                                    array = eval(valueString);
                                } else    //多次赋值
                                    array = null;
                            }
                        }
                    }
                });
                if (leftValue === undefined || leftValue === null) {      //没有找到初值定义
                    console.log("initvalue of " + leftVar + " not found\n" + generator(path.node).code);
                    return;
                }
                if (jsType === 1 && (rightValue === undefined || rightValue === null)) {      //没有找到初值定义
                    console.log("initvalue of " + rightVar + " not found\n" + generator(path.node).code);
                    return;
                }
                if (jsType === 2 && (array === undefined || array === null)) {      //没有找到初值定义
                    console.log("initvalue of " + aryVar + " not found\n" + generator(path.node).code);
                    return;
                }
                let isAssignRight = function (assNode) {
                    if (jsType === 1 && types.isAssignmentExpression(assNode)) {
                        let { operator, left, right } = assNode;
                        if (operator === "=" && types.isIdentifier(left, { name: rightVar }) &&
                            types.isNumericLiteral(right))
                            return right.value;
                    }
                };
                let caseValue = [], ret_body = [];
                if (cases.length > 0) {
                    let defaultIdx = -1;
                    for (let i = 0; i < cases.length; i++) {
                        let value, assign, body, isReturn, isContinue, isBreak;
                        if (cases[i].test === null)
                            defaultIdx = i;
                        else {
                            if (types.isNumericLiteral(cases[i].test))
                                value = cases[i].test.value;
                            else if (types.isMemberExpression(cases[i].test) &&
                                types.isArrayExpression(cases[i].test.object) && types.isNumericLiteral(cases[i].test.property)
                            ) {
                                //case [_n$jL = 33, 98][1]
                                let { elements } = cases[i].test.object, idx = cases[i].test.property.value;
                                if (elements === null || idx === null || idx < 0 || idx >= elements.length) {
                                    console.log("wrong case expression: " + generator(cases[i].test).code);
                                    return;
                                }
                                for (let i = 0; i < elements.length; i++) {
                                    let v = isAssignRight(elements[i]);
                                    if (v !== undefined) assign = v;
                                    if (i === idx) {
                                        if (v === undefined) {
                                            if (types.isNumericLiteral(elements[i]))
                                                v = elements[i].value;
                                            else
                                                v = eval(generator(elements[i]).code);
                                        }
                                        value = v;
                                    }
                                }
                            } else
                                value = eval(generator(cases[i].test).code);
                            //检查case值是否重复
                            for (let j = 0; j < i; j++) if (j != defaultIdx && value === caseValue[j].vaule) {
                                console.log("duplicate case value for " + generator(cases[j].test).code + " and " + generator(cases[i].test).code);
                                return;
                            }
                        }
                        body = [];
                        if (cases[i].consequent !== null && cases[i].consequent.length > 0) {
                            let firstLine = 0, lastLine = cases[i].consequent.length - 1;
                            //检查第一条命令是否为对变量赋值
                            if (types.isExpressionStatement(cases[i].consequent[0])) {
                                let v = isAssignRight(cases[i].consequent[0].expression);
                                if (v !== undefined) {
                                    assign = v; firstLine++;
                                }
                            }
                            //检查最后一条命令
                            if (types.isReturnStatement(cases[i].consequent[lastLine])) {
                                isReturn = true;
                            } else if (types.isContinueStatement(cases[i].consequent[lastLine])) {
                                isContinue = true; lastLine--;
                            } else if (types.isBreakStatement(cases[i].consequent[lastLine])) {
                                isBreak = true; lastLine--;
                            }
                            for (let j = firstLine; j <= lastLine; j++) body.push(cases[i].consequent[j]);
                        }
                        caseValue.push({ value, assign, body, isReturn, isContinue, isBreak });
                    }
                    let lastValue;
                    while (true) {
                        let value = array[leftValue++];
                        if (jsType === 1) {
                            if (value === undefined) {
                                console.log("switch value out of range " + leftValue + "\n" + generator(path.node).code);
                                return;
                            }
                            value ^= rightValue;
                        } else if (jsType === 2) {
                            if (value === undefined) {
                                if (defaultIdx < 0) break;  //没有default分支退出switch
                            }
                        }
                        if (value === lastValue) {    //没有改变variable的值，无限循环
                            console.log("switch value unchanged " + value + "\n" + generator(path.node).code);
                            return;
                        }
                        lastValue = value; let idx;
                        if (value != undefined)
                            for (let i = 0; i < cases.length; i++)
                                if (i != defaultIdx && caseValue[i].value === value) {
                                    idx = i; break;
                                }
                        if (idx === undefined && defaultIdx >= 0)
                            idx = defaultIdx;
                        if (idx === undefined) {
                            console.log("no case for " + value + "\n" + generator(path.node).code);
                            return;
                        }
                        if (jsType === 1 && caseValue[idx].assign !== undefined)
                            rightValue = caseValue[idx].assign;
                        if (caseValue[idx].body.length > 0)
                            ret_body.push.apply(ret_body, caseValue[idx].body);
                        if (caseValue[idx].isContinue) continue;
                        if (caseValue[idx].isBreak || caseValue[idx].isReturn) break;
                        console.log("not valid end for case " + value + "\n" + generator(path.node).code);
                        return;
                    }
                }
                part7Array.push({ path, ret_body });
            } catch (err) {
                stopWithLog("part7", err, path);
            }
        }
    });
    for (let v of part7Array) v.path.replaceInline(v.ret_body);
    outputAST(requireSteps ? (outputName + "_part7" + outputExt) : undefined);

    if (jsType === 2) {
        //删除window.console函数
        let rmvFunc = [], rmvTag = ["log", "warn", "info", "error", "exception", "table", "trace"];
        traverse(ast, {
            ArrayExpression(path) {
                let { elements } = path.node;
                if (elements.length != rmvTag.length) return;
                for (let i = 0; i < rmvTag.length; i++) if (!types.isLiteral(elements[i], { value: rmvTag[i] })) return;
                rmvFunc.push(path.getFunctionParent());
            }
        });
        for (rfunc of rmvFunc) {
            //var _$t = _$a(this, function (){...});
            //_$t();
            if (rfunc.listKey != "arguments" || rfunc.key != 1) continue;
            let path = rfunc.parentPath;
            if (!types.isCallExpression(path.node) ||
                path.node.arguments.length != 2 || !types.isThisExpression(path.node.arguments[0])) continue;
            if (!path.key === "init") continue;
            path = path.parentPath;
            if (!types.isVariableDeclarator(path.node) || !types.isIdentifier(path.node.id)) continue;
            const binding = path.scope.getBinding(path.node.id.name);
            //如果变量被修改过，则不能进行删除动作。
            if (assignTimes(binding.constantViolations, path) > 0) continue;
            let ref = [];
            for (p of binding.referencePaths) {
                let pf = p.parentPath;
                if (!types.isCallExpression(pf.node) || pf.node.arguments.length != 0) {
                    ref = undefined; break;
                }
                ref.push(pf);
            }
            if (!ref) continue;
            for (p of ref) p.remove();
            path.remove();
        }
        outputAST(requireSteps ? (outputName + "_part7_1" + outputExt) : undefined);

        for (; ;) {
            let rmv = []; traverse(ast, {
                VariableDeclarator(path) {   //删除未使用变量
                    try {
                        let parent = path.getStatementParent();     //判断是否是for (var a in ...;;)
                        if (parent !== null && (types.isForInStatement(parent) || types.isForOfStatement(parent))) return;
                        const { id } = path.node;
                        const binding = path.scope.getBinding(id.name);
                        //如果变量被修改过，则不能进行删除动作。
                        if (assignTimes(binding.constantViolations, path) > 0) return;
                        //长度为0，说明变量没有被使用过。
                        if (binding.referencePaths.length !== 0) return;
                        rmv.push(path);
                    } catch (err) {
                        stopWithLog("part7-1", err, path);
                    }
                }
            });
            if (rmv.length <= 0) break;
            for (let v of rmv) v.remove();
            outputAST(undefined);
        }
        outputAST(requireSteps ? (outputName + "_part7_2" + outputExt) : undefined);
    }

    var part8Array = [], part8Del = [];
    traverse(ast, {
        EmptyStatement(path) {  //删除空行
            try {
                part8Del.push(path);
            } catch (err) {
                stopWithLog("part8-1", err, path);
            }
        }, VariableDeclarator(path) {   //删除未使用变量
            try {
                let parent = path.getStatementParent();     //判断是否是for (var a in ...;;)
                if (parent !== null && (types.isForInStatement(parent) || types.isForOfStatement(parent))) return;
                const { id } = path.node;
                const binding = path.scope.getBinding(id.name);
                //如果变量被修改过，则不能进行删除动作。
                if (assignTimes(binding.constantViolations, path) > 0) return;
                //长度为0，说明变量没有被使用过。
                if (binding.referencePaths.length !== 0) return;
                part8Del.push(path);
            } catch (err) {
                stopWithLog("part8-2", err, path);
            }
        }, FunctionDeclaration(path) {  //删除未使用函数
            try {
                const { id } = path.node;
                const binding = path.scope.parent.getBinding(id.name);
                if (assignTimes(binding.constantViolations) > 0) return;
                if (binding.referencePaths.length !== 0) return;
                part8Del.push(path);
            } catch (err) {
                stopWithLog("part8-3", err, path);
            }
        }, NumericLiteral(path) {   //删除孤立的常数 1; 2;
            try {
                if (path.key !== 'expression' || !types.isExpressionStatement(path.parent)) return;
                part8Del.push(path);
            } catch (err) {
                stopWithLog("part8-4", err, path);
            }
        }, MemberExpression(path) {   //a['xxx'] => a.xxx
            try {
                let property = path.get('property');
                if (!property.isStringLiteral()) return;
                let value = property.node.value;
                let reg = value.match(/[A-Za-z_$][A-Za-z0-9_$]*/);
                if (reg === null || reg[0] !== value) return;    //合法变量名:[字母_$]+[字母数字_$]
                path.node.computed = false;
                let rep = types.Identifier(value);
                part8Array.push({ property, rep });
            } catch (err) {
                stopWithLog("part8-5", err, path);
            }
        }
    });
    for (let v of part8Array) v.property.replaceWith(v.rep);
    for (let v of part8Del) v.remove();
    outputAST(requireSteps ? (outputName + "_part8" + outputExt) : undefined);

    //替换逻辑表达式  
    traverse(ast, {
        ExpressionStatement(path) {
            try {
                let { expression } = path.node;
                if (types.isLogicalExpression(expression)) {
                    //xx && yy => if (xx) yy
                    //xx || yy => if (!xx) yy
                    if (isConvertableLogical(expression)) path.replaceWith(LogicalToIf(expression));
                } else if (types.isConditionalExpression(expression)) {
                    //test?consequent:alternate => if (test) consequent else alternate
                    let { consequent, alternate } = expression;
                    path.replaceWith(types.ifStatement(expression.test, ExpressionToStatement(consequent),
                        alternate ? ExpressionToStatement(alternate) : alternate));
                }
            } catch (err) {
                stopWithLog("LogicalExpression", err, path);
            }
        },
    });
    outputAST(requireSteps ? (outputName + "_part9" + outputExt) : undefined);

    //替换逗号表达式
    var part10Array = [];
    traverse(ast, {
        IfStatement(path) {     //if(xx,yy,zz) => xx;yy;if(zz)
            try {
                let { test, consequent, alternate } = path.node, needReplace;
                if (types.isSequenceExpression(consequent)) {
                    consequent = SequenceToBlock(consequent); needReplace = true;
                } else if (types.isExpressionStatement(consequent) && types.isSequenceExpression(consequent.expression)) {
                    consequent = SequenceToBlock(consequent.expression); needReplace = true;
                }
                if (types.isBlockStatement(consequent) && consequent.body.length === 1) {
                    consequent = consequent.body[0]; needReplace = true;
                }
                if (alternate) {
                    if (types.isSequenceExpression(alternate)) {
                        alternate = SequenceToBlock(alternate); needReplace = true;
                    } else if (types.isExpressionStatement(alternate) && types.isSequenceExpression(alternate.expression)) {
                        alternate = SequenceToBlock(alternate.expression); needReplace = true;
                    }
                    if (types.isBlockStatement(alternate) && alternate.body.length === 1) {
                        alternate = alternate.body[0]; needReplace = true;
                    }
                }
                let tmp = [];
                if ((path.parentKey === "body" || path.parentKey === "consequent") &&
                    types.isSequenceExpression(test) && test.expressions.length > 1) {
                    for (let i = 0; i < test.expressions.length - 1; i++)
                        tmp.push(ExpressionToStatement(test.expressions[i]));
                    test = test.expressions[test.expressions.length - 1]; needReplace = true;
                }
                if (!needReplace) return;
                tmp.push(types.ifStatement(test, consequent, alternate));
                path.replaceInline(tmp);
            } catch (err) {
                stopWithLog("SequenceIF", err, path);
            }
        },
        ReturnStatement(path) {     //return (xx,yy,zz) => xx;yy;return zz;
            try {
                if (path.parentKey !== "body" && path.parentKey !== "consequent") return;
                let { argument } = path.node;
                if (!types.isSequenceExpression(argument) || argument.expressions.length <= 1) return;
                let tmp = [];
                for (let i = 0; i < argument.expressions.length - 1; i++)
                    tmp.push(ExpressionToStatement(argument.expressions[i]));
                tmp.push(types.returnStatement(argument.expressions[argument.expressions.length - 1]));
                path.replaceInline(tmp);
            } catch (err) {
                stopWithLog("SequenceReturn", err, path);
            }
        },
        UnaryExpression(path) {     //!0=>true !1=>false
            try {
                let { node } = path;
                if (node.operator !== '!' || !types.isLiteral(node.argument)) return;
                if (node.argument.value === 0)
                    path.replaceInline(types.booleanLiteral(true));
                else if (node.argument.value === 1)
                    path.replaceInline(types.booleanLiteral(false));
            } catch (err) {
                stopWithLog("true/false", err, path);
            }
        },
        StringLiteral(path) {
            if (jsType === 2) {
                let raw = path.node.extra.raw;
                //单引号字符串换成双引号字符串
                if (raw.startsWith("'") && raw.endsWith("'")) {
                    let s = raw.substring(1, raw.length - 1); raw = '"';
                    for (let i = 0; i < s.length;) {
                        if (s[i] == '\\' && s[i + 1] == "'") {
                            raw += "'"; i += 2;
                        } else if (s[i] == '"') {
                            raw += '\\"'; i++;
                        } else {
                            raw += s[i]; i++;
                        }
                    }
                    raw += '"';
                }
                if (!raw.startsWith('"') || !raw.endsWith('"')) {
                    console.log("not valid StringLiteral:" + raw); return;
                }
                //\x20换成空格 \x0a换成\n \x22换成\"
                let newRaw = "\"";
                for (let i = 1; i < raw.length - 1;) {
                    if (raw[i] == '\\' && raw[i + 1] == 'x') {
                        if (raw[i + 2] == '2' && raw[i + 3] == '0') {
                            newRaw += ' '; i += 4; continue;
                        } else if (raw[i + 2] == '0' && raw[i + 3] == 'a') {
                            newRaw += '\\n'; i += 4; continue;
                        } else if (raw[i + 2] == '2' && raw[i + 3] == '2') {
                            newRaw += '\\"'; i += 4; continue;
                        }
                    }
                    newRaw += raw[i++];
                }
                newRaw += "\"";
                path.node.extra.raw = newRaw;
            }
        },
        NumericLiteral(path) { //16进制换成10进制
            if (jsType === 2) {
                let raw = path.node.extra.raw;
                if (raw.startsWith("0x")) path.node.extra.raw = "" + path.node.value;
            }
        },
        Property(path) {    //属性名称由字符串换成Identify
            if (jsType === 2) {
                let validKey = function (value) {
                    if (!value) return false;
                    for (let i = 0; i < value.length; i++) {
                        if ((value[i] >= 'A' && value[i] <= 'Z') || (value[i] >= 'a' && value[i] <= 'z') || value[i] == '_') continue;
                        if (value[i] < '0' || value[i] > '9' || i == 0) return false;
                    }
                    return true;
                }
                let key = path.get("key");
                if (key && types.isStringLiteral(key.node) && validKey(key.node.value) && !part10Array.includes(key))
                    part10Array.push(key);
            }
        }
    });
    for (p of part10Array) p.replaceWith(types.identifier(p.node.value));
    outputAST(requireSteps ? (outputName + "_part10" + outputExt) : undefined);

    //替换函数变量及局部变量
    let funcPath = [];
    traverse(ast, {
        FunctionExpression(path) {
            if (path.getFunctionParent() === null) funcPath.push(path);
        },
        FunctionDeclaration(path) {
            if (path.getFunctionParent() === null) funcPath.push(path);
        }
    });
    function souldReplace(_node) {
        if (!_node || !types.isIdentifier(_node)) return false;
        if (jsType === 0 || jsType === 1)
            return _node.name.length <= 2;
        else if (jsType === 2)
            return _node.name.startsWith("_") && _node.name.length <= 4;
    };
    function replaceFunction(funcPath, level, funcIDs) {
        let varPath = [], catchPath = [], subFuncPath = [];
        funcPath.traverse({
            VariableDeclarator(_path) {
                if (_path.getFunctionParent() === funcPath) varPath.push(_path);
            },
            FunctionDeclaration(_path) {
                if (_path.getFunctionParent() === funcPath) subFuncPath.push(_path);
            },
            FunctionExpression(_path) {
                if (_path.getFunctionParent() === funcPath) subFuncPath.push(_path);
            },
            CatchClause(_path) {
                if (_path.getFunctionParent() === funcPath) catchPath.push(_path);
            }
        });
        //替换function
        let subIDs = { id: 1, orgId: 1 };
        for (let i = 0; i < subFuncPath.length; i++)
            subIDs = replaceFunction(subFuncPath[i], level + 1, subIDs);
        let levelString = String.fromCharCode("A".charCodeAt() + level);
        function isInsSymbol(symbolName, node) {
            for (let v of insSymbols) if (v.key === symbolName) {
                if (!v.extra) return true;
                if (generator(node).code.indexOf(v.extra) >= 0) return true;
            }
        }
        function isDelSymbol(symbolName) {
            for (let v of delSymbols) if (v === symbolName) return true;
        }
        //替换arg
        let params = funcPath.get("params"); if (params) for (let varId = 1, orgId = 1, i = 0; i < params.length; i++) {
            let arg = params[i]; if (!souldReplace(arg.node)) continue;
            let newName = "_arg" + levelString + orgId;
            if (isInsSymbol(newName, arg.node))
                newName += "_sp";
            else {
                if (isDelSymbol(newName)) varId++;
                newName = "_arg" + levelString + varId; varId++;
            }
            funcPath.scope.rename(arg.node.name, newName); orgId++;
        }
        //替换var
        for (let varId = 1, orgId = 1, i = 0; i < varPath.length; i++) if (souldReplace(varPath[i].node.id)) {
            let newName = "_lcl" + levelString + orgId;
            if (isInsSymbol(newName, varPath[i].node))
                newName += "_sp";
            else {
                if (isDelSymbol(newName)) varId++;
                newName = "_lcl" + levelString + varId; varId++;
            }
            funcPath.scope.rename(varPath[i].node.id.name, newName); orgId++;
        }
        //替换Catch
        for (let varId = 1, orgId = 1, i = 0; i < catchPath.length; i++) if (souldReplace(catchPath[i].node.param)) {
            let newName = "_err" + levelString + orgId;
            if (isInsSymbol(newName, catchPath[i].node))
                newName += "_sp";
            else {
                if (isDelSymbol(newName)) varId++;
                newName = "_err" + levelString + varId; varId++;
            }
            catchPath[i].scope.rename(catchPath[i].node.param.name, newName); orgId++;
        }
        //替换id
        if (souldReplace(funcPath.node.id)) {
            let newName = "Func_" + (level > 0 ? String.fromCharCode("A".charCodeAt() + level - 1) : "") + funcIDs.orgId;
            if (isInsSymbol(newName, funcPath.node))
                newName += "_sp";
            else {
                if (isDelSymbol(newName)) funcIDs.id++;
                newName = "Func_" + (level > 0 ? String.fromCharCode("A".charCodeAt() + level - 1) : "") + funcIDs.id; funcIDs.id++;
            }
            funcPath.scope.rename(funcPath.node.id.name, newName); funcIDs.orgId++;
        }
        return funcIDs;
    };
    let IDs = { id: 1, orgId: 1 };
    for (let i = 0; i < funcPath.length; i++)
        IDs = replaceFunction(funcPath[i], 0, IDs);
    outputAST(requireSteps ? (outputName + outputExt) : undefined);
    return true;
}


let fileName, fileExt, outputName, requireSteps = false, copyAnyway = false;
for (let i = 0; i < args.length; i++) {
    if (args[i].toLowerCase() === "/step")
        requireSteps = true;
    else if (args[i].toLowerCase() === "/copy")
        copyAnyway = true;
    else if (args[i].toLocaleLowerCase().substring(0, 3) === "/i=") {
        let ary = args[i].substring(3).split(",");
        if (ary) for (s of ary) {
            let idx = s.indexOf("@"), key = s, extra;
            if (idx >= 0) { key = s.substring(0, idx); extra = s.substring(idx + 1); }
            insSymbols.push({ key, extra });
        }
    } else if (args[i].toLocaleLowerCase().substring(0, 3) === "/d=")
        delSymbols = args[i].substring(3).split(",");
    else if (!fileName) {
        let files = glob.sync(args[i]);
        if (files && files.length > 0) {
            fileName = files[0];
            if (fileName.endsWith(".htm")) fileExt = ".htm";
            else if (fileName.endsWith(".html")) fileExt = ".html";
            else fileExt = ".js";
            if (fileName.endsWith(fileExt)) fileName = fileName.substring(0, fileName.length - fileExt.length);
        }
    } else if (!outputName)
        outputName = args[i];
}
do {
    if (!fileName) {
        console.log("usage: node decoder.js <FILEforDecode>[.js/.htm/.html] [outputFile[.js/.htm/.html]] [/i=insSymbols] [/d=delSymbols] [/step] [/copy]");
        break;
    }
    if (!outputName) outputName = fileName;
    if (outputName.endsWith(fileExt)) outputName = outputName.substring(0, outputName.length - fileExt.length);
    let data = fs.readFileSync(fileName + fileExt, 'utf8');
    if (fileExt === ".js") {
        if (!procDecode(data, outputName, fileExt, requireSteps)) {
            if (copyAnyway) fs.writeFileSync(outputName + fileExt, data, 'utf8');
            break;
        }
        if (!requireSteps) fs.writeFileSync(outputName + fileExt, astCode, 'utf8');
    } else {
        let $ = cheerio.load(data);
        $("script").each(function (i, elem) {
            $(this).contents().each(function (_i, _elem) {
                if (this.type !== 'text') return;
                let txt = this.data; if (!txt || txt.length <= 0) return;
                if (!procDecode(txt, outputName, fileExt, false)) return;
                this.data = astCode;
            });
        });
        fs.writeFileSync(outputName + fileExt, html_beautify($.html()), 'utf8');
    }
    console.log("Decode successful, output file: " + outputName + fileExt);
} while (0)
